﻿// -----------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.Internal;

namespace System.ComponentModel.Composition
{
    partial class AdaptingExportProvider
    {
        // Adapts exports and contract names based on present adapters
        private class AdapterManager : ComposablePart
        {
            private static readonly ImportDefinition[] _imports = { CreateAdapterImport() };
            private AdapterDefinition[] _adapters;
            private readonly AdaptingExportProvider _provider;

            public AdapterManager(AdaptingExportProvider provider)
            {
                Assumes.NotNull(provider);

                this._provider = provider;
            }

            private IEnumerable<AdapterDefinition> Adapters
            {
                get
                {
                    if (this._adapters == null)
                    {
                        // Prevent us from re-composing this part when we're 
                        // in the middle of composing adapters.
                        this._adapters = new AdapterDefinition[0];
                        this._provider.CompositionEngine.SatisfyImportsAndRegisterForRecomposition(this);
                    }

                    return this._adapters;
                }
            }

            public override IEnumerable<ImportDefinition> ImportDefinitions
            {
                get { return _imports; }
            }

            public override void SetImport(ImportDefinition definition, IEnumerable<Export> exports)
            {
                Assumes.NotNull(definition, exports);
                Assumes.IsTrue(definition == _imports[0]);

                IEnumerable<AdapterDefinition> oldAdapters = _adapters;

                // TODO: When warnings are re-added, we should add warnings for adapters with null or empty to/from contract names
                _adapters = exports.Select(export => new AdapterDefinition(export))
                                   .Where(adapter => adapter.HasValidMetadata())
                                   .ToArray();
                            
                // As the contract names that adapter adapts from/to are not considered as 'actual' 
                // imports and exports, we need to manually recompose these when adapters change.                
                IEnumerable<string> changedContractNames = GetChangedContractNames(oldAdapters, _adapters);
                if (changedContractNames.Any())
                {
                    this._provider.OnExportsChangedInternal(this, new ExportsChangedEventArgs(changedContractNames));
                }
            }

            public CompositionResult<IEnumerable<Export>> AdaptExports(IEnumerable<Export> exports, ImportDefinition importDefinition)
            {
                Assumes.NotNull(exports, importDefinition);

                var result = CompositionResult.SucceededResult;

                IEnumerable<AdapterDefinition> adapters = this.GetFilteredAdapters(importDefinition);
                if (adapters.Any())
                {
                    Func<ExportDefinition, bool> compiledConstraint = importDefinition.Constraint.Compile();

                    foreach (AdapterDefinition adapter in adapters)
                    {
                        CompositionResult<IEnumerable<Export>> adaptedExportsResult = this.GetAdaptedExportsFromAdapter(adapter, compiledConstraint);
                        result = result.MergeResult(adaptedExportsResult.ToResultWithNoValue());

                        IEnumerable<Export> adaptedExports = adaptedExportsResult.Value;

                        exports = exports.Concat(adaptedExports);
                    }
                }

                return result.ToResult<IEnumerable<Export>>(exports);
            }

            public IEnumerable<string> AdaptContractNames(IEnumerable<string> changedContractNames)
            {
                // When the contract name that an adapter adapts-from changes, 
                // the associated contract name an adapter adapts-to should also be 
                // considered as changed.

                Assumes.NotNull(changedContractNames);

                List<string> newContractNames = new List<string>();
                foreach (AdapterDefinition adapter in this.Adapters)
                {
                    foreach (string contractName in changedContractNames)
                    {
                        if (StringComparers.ContractName.Equals(adapter.FromContractName, contractName))
                        {
                            newContractNames.Add(adapter.ToContractName);
                        }
                    }
                }

                return changedContractNames.Concat(newContractNames);
            }

            private static ImportDefinition GetAdapterExportsImport(AdapterDefinition adapter)
            {
                return new ContractBasedImportDefinition(adapter.FromContractName, 
                                                         ImportCardinality.ZeroOrMore,
                                                         false,     /* Recomposable */
                                                         false);    /* Prerequisite */
            }

            private CompositionResult<IEnumerable<Export>> GetAdaptedExportsFromAdapter(AdapterDefinition adapter, Func<ExportDefinition, bool> constraint)
            {
                // Query the container for all exports that the specified adapter 
                // can adapt, then run each one through the adapter, returning the ones 
                // that come out as non-null and also meet the specified constraint

                Assumes.NotNull(adapter, constraint);

                var result = CompositionResult.SucceededResult;

                ImportDefinition adapterImport = GetAdapterExportsImport(adapter);

                List<Export> adaptedExports = new List<Export>();

                CompositionResult<IEnumerable<Export>> exportsToAdaptResult =  CompositionServices.TryInvoke(() => this._provider.SourceProvider.GetExports(adapterImport));
                if (exportsToAdaptResult.Succeeded)
                {
                    IEnumerable<Export> exportsToAdapt = exportsToAdaptResult.Value;
                    foreach (Export exportToAdapt in exportsToAdapt)
                    {
                        CompositionResult<Export> adaptedExportResult = adapter.Adapt(exportToAdapt);
                        if (!adaptedExportResult.Succeeded)
                        {
                            // Once we've hit an error, skip this adapter
                            return result.MergeResult(adaptedExportResult.ToResultWithNoValue()).ToResult(Enumerable.Empty<Export>());
                        }

                        Export adaptedExport = adaptedExportResult.Value;
                        if (adaptedExport != null && constraint(adaptedExport.Definition))
                        {
                            adaptedExports.Add(adaptedExport);
                        }
                    }
                }
                else
                {
                    result = result.MergeIssues(exportsToAdaptResult.Issues);
                }

                return result.ToResult<IEnumerable<Export>>(adaptedExports);
            }

            private IEnumerable<AdapterDefinition> GetFilteredAdapters(ImportDefinition definition)
            {
                // If we have access to the contract name, such as pulling it directly from the import,
                // then we can query the adapters based on that, otherwise, we have to return them all.
                ContractBasedImportDefinition contractBasedDefinition = definition as ContractBasedImportDefinition;
                if (contractBasedDefinition == null)
                {
                    return this.Adapters;
                }

                return this.Adapters.Where(adapter =>
                {
                    return StringComparers.ContractName.Equals(adapter.ToContractName, contractBasedDefinition.ContractName);
                });
            }

            private static IEnumerable<string> GetChangedContractNames(IEnumerable<AdapterDefinition> oldAdapters, IEnumerable<AdapterDefinition> newAdapters)
            {
                Assumes.NotNull(oldAdapters, newAdapters);

                // Do a symmetric difference to find out what adapters have changed
                IEnumerable<string> removedContractNames = oldAdapters.Where(oldAdapter => !newAdapters.Any(newAdapter => newAdapter.Export == oldAdapter.Export))
                                                                      .Select(oldAdapter => oldAdapter.ToContractName);

                IEnumerable<string> addedContractNames = newAdapters.Where(newAdapter => !oldAdapters.Any(oldAdapter => oldAdapter.Export == newAdapter.Export))
                                                                    .Select(newAdapter => newAdapter.ToContractName);

                return removedContractNames.Concat(addedContractNames);
            }

            private static ImportDefinition CreateAdapterImport()
            {
                // Generates the following constraint:
                // item => item.ContractName == "Adapter" && item.Metadata.ContainsKey("From") && item.Metadata.ContainsKey("To");
                return new ContractBasedImportDefinition(CompositionServices.AdapterContractName,
                                                         new string[] { CompositionServices.AdapterFromContractMetadataName,
                                                                        CompositionServices.AdapterToContractMetadataName },
                                                         ImportCardinality.ZeroOrMore,
                                                         true,                              /* Recomposable */
                                                         false);                            /* Prerequisite */
            }

            #region ComposablePart overrides

            public override IDictionary<string, object> Metadata
            {
                get { return MetadataServices.EmptyMetadata; }
            }

            public override IEnumerable<ExportDefinition> ExportDefinitions
            {
                get { return Enumerable.Empty<ExportDefinition>(); }
            }

            public override object GetExportedObject(ExportDefinition definition)
            {
                Assumes.Fail("");
                return null;
            }

            #endregion
        }
    }
}
